﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace ParticlesEngine {

    abstract class ParticlesSystem {
        protected int numParticles;
        private int numNewParticles=0;
        int maxParticles;
        ParticleEmitter emitter;
        Particle[] particlesList;
        bool enabled;
        // Store the current time, in seconds.
        float currentTime;

        bool newParticleAdded;
        int lastNumParticles;

        //render
        //Effect particleEffect;
        Effect particleEffect;
        DynamicVertexBuffer vertexBuffer;
        VertexDeclaration vertexDeclaration;
        Texture2D particleTexture = null;

        const int PARTICLES_SET = 2;
      
        public ParticlesSystem(int max) {
            maxParticles = max;
            numParticles = 0;
            lastNumParticles = 0;
            numNewParticles = 0;
        }
        public abstract void update(double elapsedTime);
        public abstract void initialize();
        public abstract void render();

        public virtual void updateSystem(double elapsedTime){
            currentTime += (float)elapsedTime;
            //removing particles with energy <= 0
            removeDeathParticles();
            update(elapsedTime);
            //move();
        }
        //TODO esto se tendria que hacer por shader, el mover las particulas o hacer unos calculos senzillos,
        //considerando todas las variables.
        //private void move()
        //{
        //    for (int i = 0; i < numParticles; ++i)
        //    {
        //        Particle p = particlesList[i];
        //        p.previousPosition = p.position;
        //        p.position = p.position + p.velocity;
        //        p.energy = p.energy - 1;
        //        particlesList[i] = p;
        //    }
        //}
        private void removeDeathParticles(){
            for (int i = 0; i < numParticles; ++i){
                Particle particle = particlesList[i];
                if (particle.time+particle.timeLife <= currentTime) removeParticle(i);
            }
        }
        void setParticleRenderStates(RenderState renderState)
        {
            // Enable point sprites.
            renderState.PointSpriteEnable = true;
            renderState.PointSizeMax = 256;

            // Set the alpha blend mode.
            renderState.AlphaBlendEnable = true;
            renderState.AlphaBlendOperation = BlendFunction.Add;
            renderState.SourceBlend = Blend.SourceAlpha;
            renderState.DestinationBlend = Blend.InverseSourceAlpha;

            // Set the alpha test mode.
            renderState.AlphaTestEnable = true;
            renderState.AlphaFunction = CompareFunction.Greater;
            renderState.ReferenceAlpha = 0;

            // Enable the depth buffer (so particles will not be visible through
            // solid objects like the ground plane), but disable depth writes
            // (so particles will not obscure other particles).
            renderState.DepthBufferEnable = true;
            renderState.DepthBufferWriteEnable = false;
        }
        private void addNewParticles()
        {
            //añadir 10 particulas y llamar al setdata. 
            if (numNewParticles == PARTICLES_SET)
            {
                vertexBuffer.SetData<Particle>(Particle.SizeInBytes * (numParticles - PARTICLES_SET), particlesList, numParticles - PARTICLES_SET, PARTICLES_SET, Particle.SizeInBytes, SetDataOptions.NoOverwrite);
                lastNumParticles = numParticles;
                numNewParticles = 0;
            }
        }
        public virtual void renderSystem(){
            GraphicsDeviceManager graphics = ParticlesSystemEngine.GetInstance().getGraphicsDeviceManager();
            GraphicsDevice device = graphics.GraphicsDevice;

            if (numParticles <= 0) return;

            render();

            if (vertexBuffer.IsContentLost){
                vertexBuffer.SetData<Particle>(particlesList);
            }
            //looking for new particles
            addNewParticles();
            setParticleRenderStates(device.RenderState);

            particleEffect.Parameters["ViewportHeight"].SetValue(device.Viewport.Height);
            particleEffect.Parameters["CurrentTime"].SetValue(currentTime);
            //particleEffect.GraphicsDevice.RenderState.PointSize = 1f;

            //esta linea igual no hace falta.
            particleEffect.GraphicsDevice.VertexDeclaration = vertexDeclaration;
            
            device.Vertices[0].SetSource(vertexBuffer, 0, Particle.SizeInBytes);
            device.VertexDeclaration = vertexDeclaration;

            //drawing process with a Effect shader
            particleEffect.Begin();
            foreach (EffectPass pass in particleEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                device.DrawPrimitives(PrimitiveType.PointList, 0, numParticles);
                pass.End();
            }
            particleEffect.End();
            device.RenderState.PointSpriteEnable = false;
            device.RenderState.DepthBufferWriteEnable = true;
        }
        
        public virtual void initializeSystem(){
            System.Console.Out.WriteLine("[ParticlesSystem][initialize]");
            if (maxParticles <= 0) return;

            if (particlesList != null) destroy();
            else {
                GraphicsDeviceManager graphics = ParticlesSystemEngine.GetInstance().getGraphicsDeviceManager();
                GraphicsDevice device = graphics.GraphicsDevice;
                
                particlesList = new Particle[maxParticles];
                vertexDeclaration = new VertexDeclaration(device, Particle.VertexElements);

                // Create a dynamic vertex buffer.
                int size = Particle.SizeInBytes * particlesList.Length;

                vertexBuffer = new DynamicVertexBuffer(device, size, BufferUsage.WriteOnly | BufferUsage.Points);

                //initializing the specific system
                initialize();

                //loading particle effect 
                loadParticleEffect();
                //createEffect();
            }
        }
        void loadParticleEffect()
        {
            GraphicsDeviceManager graphics = ParticlesSystemEngine.GetInstance().getGraphicsDeviceManager();
            GraphicsDevice device = graphics.GraphicsDevice;
            ContentManager content = ParticlesSystemEngine.GetInstance().getContentManager();
            Effect effect = content.Load<Effect>("ParticleEffect");

            // If we have several particle systems, the content manager will return
            // a single shared effect instance to them all. But we want to preconfigure
            // the effect with parameters that are specific to this particular
            // particle system. By cloning the effect, we prevent one particle system
            // from stomping over the parameter settings of another.

            particleEffect = effect.Clone(device);
            
            EffectParameterCollection parameters = particleEffect.Parameters;

            Matrix worldMatrix = ParticlesSystemEngine.GetInstance().getWorldMatrix();
            Matrix viewMatrix = ParticlesSystemEngine.GetInstance().getViewMatrix();
            Matrix projectionMatrix = ParticlesSystemEngine.GetInstance().getProjectionMatrix();

            //// Look up shortcuts for parameters that change every frame.
            parameters["View"].SetValue(viewMatrix);
            parameters["Projection"].SetValue(projectionMatrix);
            parameters["ViewportHeight"].SetValue(device.Viewport.Height);

            // Set the values of parameters that do not change.
            
            parameters["Duration"].SetValue(10);
            parameters["Gravity"].SetValue(new Vector3(-5.0f, -1.8f, 0.0f));
            parameters["EndVelocity"].SetValue(10.075f);

            parameters["StartSize"].SetValue(
               new Vector2(5, 10));

            parameters["EndSize"].SetValue(
                new Vector2(50,200));
            /*
            parameters["DurationRandomness"].SetValue(settings.DurationRandomness);
            parameters["Gravity"].SetValue(settings.Gravity);
            parameters["EndVelocity"].SetValue(settings.EndVelocity);
            parameters["MinColor"].SetValue(settings.MinColor.ToVector4());
            parameters["MaxColor"].SetValue(settings.MaxColor.ToVector4());

            parameters["RotateSpeed"].SetValue(
                new Vector2(settings.MinRotateSpeed, settings.MaxRotateSpeed));

           */

            parameters["Texture"].SetValue(particleTexture);

            // Choose the appropriate effect technique. If these particles will never
            // rotate, we can use a simpler pixel shader that requires less GPU power.
            string techniqueName;

            //if ((settings.MinRotateSpeed == 0) && (settings.MaxRotateSpeed == 0))
            //    techniqueName = "NonRotatingParticles";
            //else
                techniqueName = "RotatingParticles";

            particleEffect.CurrentTechnique = particleEffect.Techniques[techniqueName];
        }
        //TODO esto habria que pensarlo un poco mejor, se supone que un Effect tiene asociado un shader
        /*private void createEffect(){
            GraphicsDeviceManager graphics = ParticlesSystemEngine.GetInstance().getGraphicsDeviceManager();
            GraphicsDevice device = graphics.GraphicsDevice;
            Matrix worldMatrix = ParticlesSystemEngine.GetInstance().getWorldMatrix();
            Matrix viewMatrix = ParticlesSystemEngine.GetInstance().getViewMatrix();
            Matrix projectionMatrix = ParticlesSystemEngine.GetInstance().getProjectionMatrix();

            particleEffect = new BasicEffect(device, null);
            particleEffect.Alpha = 1.0f;
            particleEffect.DiffuseColor = new Vector3(1.0f, 0.0f, 1.0f);
            particleEffect.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
            particleEffect.SpecularPower = 5.0f;
            particleEffect.AmbientLightColor = new Vector3(0.75f, 0.75f, 0.75f);

            particleEffect.DirectionalLight0.Enabled = true;
            particleEffect.DirectionalLight0.DiffuseColor = Vector3.One;
            particleEffect.DirectionalLight0.Direction =
                Vector3.Normalize(new Vector3(1.0f, -1.0f, -1.0f));
            particleEffect.DirectionalLight0.SpecularColor = Vector3.One;

            particleEffect.DirectionalLight1.Enabled = true;
            particleEffect.DirectionalLight1.DiffuseColor =
                new Vector3(0.5f, 0.5f, 0.5f);
            particleEffect.DirectionalLight1.Direction =
                Vector3.Normalize(new Vector3(-1.0f, -1.0f, 1.0f));
            particleEffect.DirectionalLight1.SpecularColor =
                new Vector3(0.5f, 0.5f, 0.5f);

            particleEffect.LightingEnabled = true;

            particleEffect.World = worldMatrix;
            particleEffect.View = viewMatrix;
            particleEffect.Projection = projectionMatrix;
        }*/
        public void enable(bool value) { enabled = value; }
        public bool isEnabled() { return enabled; }

        protected void addParticle(Particle p){
            if (numParticles >= maxParticles) return;
            
            particlesList[numParticles] = p;
            ++numParticles;
            ++numNewParticles;
        }
        protected void addParticle(Vector3 velocity, Vector3 acceleration, float timeLife, float size, float weight, Color color, Color delta)
        {
            if (emitter == null) return;

            Vector3 position = emitter.getPosition();

            // creation of a new particle.
            Particle particle = new Particle();
            particle.position = position;
            particle.velocity = velocity;
            particle.acceleration = acceleration;
            particle.timeLife = timeLife;
            particle.size = size;
            particle.weight = weight;
            particle.color = color;
            particle.delta = delta;
            particle.time = (float)currentTime;
            Random random = new Random();
            particle.random = new Color((byte)random.Next(255),
                                           (byte)random.Next(255),
                                           (byte)random.Next(255),
                                           (byte)random.Next(255));
            addParticle(particle);
        }
        protected void removeParticle(int index){
            if (index < 0 || index > maxParticles) return;

            if (numParticles > 0) particlesList[index] = particlesList[numParticles - 1];
            --numParticles;
        }
        protected int getMaxParticles() { return maxParticles; }
        protected int getNumParticles() { return numParticles; }
        protected ParticleEmitter getParticleEmitter() { return emitter; }
        public void addParticleEmitter(ParticleEmitter pe) { emitter = pe; }
        protected Particle getParticle(int i) { return particlesList[i]; }

        protected void load(String texture){
            if (texture == null) return;

            try
            {
                //TODO
                //content = ResourcesManager.getContentManager();
                //eliminar la definicion de la variable content
                ContentManager content = ParticlesSystemEngine.GetInstance().getContentManager();
                particleTexture = content.Load<Texture2D>(texture);
            }catch(Exception e){
                System.Console.Out.WriteLine("[ParticlesSystem][load] Exception ", e.Message);
            }
        }
        public virtual void destroy(){
            //TODO: como marco para eliminar algo que he pillado con memoria dinamica
            particlesList = null;
            numParticles = 0;
        }
    }
}
